/**********************************************************************************
// Input Source
// 
// Creation:	14 Oct 2007
// Updated:		17 Abr 2011
// Compiler:	Visual Studio 2010
//
// Notes:		Input control class.
//				Manages keyboard, mouse and joystick input
//
**********************************************************************************/

#include "Input.h"

/**********************************************************************************/

Input::Input()
{
	lpDI        = NULL;	// initialize direct input interface pointer
	lpDIkey     = NULL;	// initialize keyboard device pointer
	lpDImouse   = NULL;	// initialize mouse interface pointer
	lpDIjoy     = NULL;	// initialize joystick interface pointer
}

/**********************************************************************************/

Input::~Input()
{
	// unacquire the joystick
	if (lpDIjoy)
		lpDIjoy->Unacquire();

	// release the joystick device
	if (lpDIjoy)
		lpDIjoy->Release();

	// unacquire the mouse
	if (lpDImouse)
		lpDImouse->Unacquire();

	// release the mouse device
	if (lpDImouse)
		lpDImouse->Release();

	// unacquire the keyboard
	if (lpDIkey)
		lpDIkey->Unacquire();

	// release the keyboard device
	if (lpDIkey)
		lpDIkey->Release();

	// release the direct input interface object
	if (lpDI)
		lpDI->Release();
}

/**********************************************************************************/

bool Input::Initialize()
{
	// create the direct input interface object
	if FAILED(DirectInput8Create(WindowsGame::GetInstance(),  
		                         DIRECTINPUT_VERSION, 
								 IID_IDirectInput8, 
								 (void **) &lpDI, 
								 NULL))
		return false;

	
	// search for attached joystick devices 
	// using EnumerateJoysticks callback function
	if (FAILED(lpDI->EnumDevices(DI8DEVCLASS_GAMECTRL,			// joysticks only
		                         JoystickEnumerate,				// enumeration function
								 &joysticks,					// send joystick list back in this address
								 DIEDFL_ATTACHEDONLY)))			// attched devices only
		return false;

	// select the first found joystick
	selectedJoystick = joysticks.begin();

	// sucessful initialization
	return true;
}

/**********************************************************************************/

bool Input::InitializeKeyboard()
{
	// If DirectInput is not initialized exit
	if (!lpDI)
		return false;

	// create the keyboard device
	if (FAILED(lpDI->CreateDevice(GUID_SysKeyboard, &lpDIkey, NULL)))
		return false;

	// set cooperation level for keyboard
	if (FAILED(lpDIkey->SetCooperativeLevel(WindowsGame::GetWindowHandle(), 
		DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)))
		return false;

	// set data format for keyboard
	if (FAILED(lpDIkey->SetDataFormat(&c_dfDIKeyboard)))
		return false;

	// acquire the keyboard
	if (FAILED(lpDIkey->Acquire()))
		return false;

	// reset keyctrl state array
	KeyReset();

	// sucessful initialization
	return true;
}

/**********************************************************************************/

bool Input::InitializeMouse()
{
	// If DirectInput is not initialized exit
	if (!lpDI)
		return false;

	// create the mouse device
	if (FAILED(lpDI->CreateDevice(GUID_SysMouse, &lpDImouse, NULL)))
		return false;

	// set cooperation level for mouse
	if (FAILED(lpDImouse->SetCooperativeLevel(WindowsGame::GetWindowHandle(), 
		DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)))
		return false;

	// set data format for mouse
	if (FAILED(lpDImouse->SetDataFormat(&c_dfDIMouse)))
		return false;

	// acquire the mouse
	if (FAILED(lpDImouse->Acquire()))
		return false;

	// sucessful initialization
	return true;
}

/**********************************************************************************/

bool Input::InitializeJoystick()
{
	// unacquire the previous selected joystick
	if (lpDIjoy)
		lpDIjoy->Unacquire();

	// release the previous selected joystick
	if (lpDIjoy)
		lpDIjoy->Release();

	// check if joystick is present
	if (joysticks.empty())
		return false;

	// create the joystick device 
	if (FAILED(lpDI->CreateDevice((*selectedJoystick).guid, &lpDIjoy, NULL)))
		return false;

	// set cooperation level for joystick
	if (FAILED(lpDIjoy->SetCooperativeLevel(WindowsGame::GetWindowHandle(), 
		DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)))
		return false;

	// set data format
	if (FAILED(lpDIjoy->SetDataFormat(&c_dfDIJoystick)))
		return false;

	// Enumerate the joystick objects. The callback function enables user
    // interface to know which objects exists in the joysticks that are found.
	joystickObjects.clear();
    if(FAILED(lpDIjoy->EnumObjects(JoystickEnumObjects, &joystickObjects, DIDFT_ALL)))
		return false;

	
    // for each axis, set the DIPROP_RANGE property
    // in order to scale min/max values and to define the deadzone.
    DIPROPRANGE joyAxisRange; 
	DIPROPDWORD deadZone;

	list<JoyObjs>::const_iterator i;
	for (i = joystickObjects.begin(); i != joystickObjects.end(); ++i)
	{
		if (i->type & DIDFT_AXIS)
		{
			// axis range
			joyAxisRange.diph.dwSize       = sizeof(DIPROPRANGE); 
			joyAxisRange.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
			joyAxisRange.diph.dwHow        = DIPH_BYID; 
			joyAxisRange.diph.dwObj        = i->type;
			joyAxisRange.lMin              = -1000; 
			joyAxisRange.lMax              = +1000; 
    
			// set the range for the axis
			if (FAILED(lpDIjoy->SetProperty(DIPROP_RANGE, &joyAxisRange.diph))) 
				return false;

			// deadzone
			deadZone.diph.dwSize       = sizeof(deadZone);
			deadZone.diph.dwHeaderSize = sizeof(deadZone.diph);
			deadZone.diph.dwHow        = DIPH_BYID;
			deadZone.diph.dwObj        = i->type;
			deadZone.dwData            = 2000;

			// set the deadzone for the axis
			if (FAILED(lpDIjoy->SetProperty(DIPROP_DEADZONE, &deadZone.diph)))
				return false;
		}
	}

	// acquire the joystick
	if(FAILED(lpDIjoy->Acquire()))
		return false;

	// sucessful initialization
	return true;
}

/**********************************************************************************/

bool Input::InitializeXbox360(int controller)
{
	XINPUT_CAPABILITIES caps;
	ZeroMemory(&caps, sizeof(XINPUT_CAPABILITIES));
	XInputGetCapabilities(controller, XINPUT_FLAG_GAMEPAD, &caps);

	if (caps.SubType != XINPUT_DEVSUBTYPE_GAMEPAD)
		return false;

	return true;
}

/**********************************************************************************/

bool Input::GetKeyboardState()
{
	HRESULT result;

	// let's read the keyboard
	while (result = lpDIkey->GetDeviceState(256, (LPVOID) keystate) == DIERR_INPUTLOST)
	{
		// try to reaquire the device if it was lost
		if (FAILED(result = lpDIkey->Acquire()))
			break;	// serius error
	}
	
	if (FAILED(result))
		return false;

	// sucessful reading
	return true;
}

/**********************************************************************************/

bool Input::GetMouseState()
{
	HRESULT result;

	// reading the mouse
	while (result = lpDImouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID) &mousestate) == DIERR_INPUTLOST)
	{
		// try to reaquire the device if it was lost
		if (FAILED(result = lpDImouse->Acquire()))
			break;	// serius error
	}
	
	if (FAILED(result))
		return false;

	// sucessful reading
	return true;
}

/**********************************************************************************/

bool Input::GetJoystickState()
{
	HRESULT result;

	// always poll the jostick devices before reading their state
	if (FAILED(lpDIjoy->Poll()))
		return false;

	// reading the joystick
	while (result = lpDIjoy->GetDeviceState(sizeof(DIJOYSTATE), (LPVOID) &joystate) == DIERR_INPUTLOST)
	{
		// try to reaquire the device if it was lost
		if (FAILED(result = lpDIjoy->Acquire()))
			break;	// serius error

		// always poll the jostick devices
		if (FAILED(lpDIjoy->Poll()))
		return false;
	}
	
	if (FAILED(result))
		return false;
	
	// sucessful reading
	return true;
}

/**********************************************************************************/

bool Input::GetXbox360State(int controller)
{
	ZeroMemory(&xbox360state, sizeof(XINPUT_STATE));
	DWORD result = XInputGetState(controller, &xbox360state);

	// if result is zero the controller was read sucessfully
	if (result)
		return false;
	
	// sucessful reading
	return true;
}

/**********************************************************************************/
// get state of the discretized key (register another press only after release)

bool Input::KeyPressRelease(int key)
{
	if (keyctrl[key])
	{
		if (KeyReleased(key))
			keyctrl[key] = false;
	}
	else
	{
		if (KeyPressed(key))
		{
			keyctrl[key] = true;
			return true;
		}
	}

	return false;
}

/**********************************************************************************/

bool Input::MouseButton(int button) 
{ 
	return mousestate.rgbButtons[button] && 0x80;  
}

/**********************************************************************************/

long Input::MouseAxis(int axis)
{ 
	switch (axis)
	{
	case MouseAxisX:
		return mousestate.lX; 
	case MouseAxisY:
		return mousestate.lY; 
	case MouseWheel:
		return mousestate.lZ;
	default:
		return 0;
	}	
}

/**********************************************************************************/

bool Input::JoystickButton(int button)
{
	if (button < 32)
		return joystate.rgbButtons[button] && 0x80;
	else
		return 0;
}

/**********************************************************************************/

long Input::JoystickAxis(int axis)
{ 
	switch (axis)
	{
	case JoyAxisX:
		return joystate.lX; 
	case JoyAxisY:
		return joystate.lY; 
	case JoyAxisZ:
		return joystate.lZ;
	case JoyAxisRX:
		return joystate.lRx; 
	case JoyAxisRY:
		return joystate.lRy; 
	case JoyAxisRZ:
		return joystate.lRz;

	default:
		return 0;
	}	
}

/**********************************************************************************/

long Input::JoystickPov(int pov)
{
	if (pov < 4)
		return joystate.rgdwPOV[pov];
	else 
		return 0;
}

/**********************************************************************************/

// select next joystick from the list
bool Input::JoystickSelectNext()
{
	++selectedJoystick;

	if (selectedJoystick == joysticks.end())
		selectedJoystick = joysticks.begin();		

	if (InitializeJoystick())
		return true;

	// selection failed
	return false;
}

/**********************************************************************************/

// select previous joystick from the list
bool Input::JoystickSelectPrev()
{
	if (selectedJoystick == joysticks.begin())
		selectedJoystick = joysticks.end();

	--selectedJoystick;
	
	if (InitializeJoystick())
		return true;

	// selection failed
	return false;
}

/**********************************************************************************/

BOOL CALLBACK JoystickEnumerate(LPCDIDEVICEINSTANCE lpDDi, LPVOID data)
{
	// this function enumerates the joysticks found in the system 
	JoyInfo joy;

	// copy guid instance
	joy.guid = lpDDi->guidInstance;
	// copy product name
	joy.name = string(lpDDi->tszInstanceName);

	// push joystick into the list
	((list<JoyInfo>*) data)->push_back(joy);

	// continue enumeration until the end
	return DIENUM_CONTINUE;
}

/**********************************************************************************/

BOOL CALLBACK JoystickEnumObjects(LPCDIDEVICEOBJECTINSTANCE lpDIdoi, LPVOID data)
{
	// this function enumerates the joystick elements
	// like buttons, axis, povs and sliders
	JoyObjs button;

	// copy guid type
	button.guid = lpDIdoi->guidType;
	// copy type
	button.type = lpDIdoi->dwType;
	// copy product name
	button.name = string(lpDIdoi->tszName);
	
	if (lpDIdoi->guidType != GUID_Unknown)
		// push joystick into the list
		((list<JoyObjs>*) data)->push_back(button);

	// continue enumeration until the end
	return DIENUM_CONTINUE;
}

/**********************************************************************************/

int Input::Xbox360Button(int button)
{
	switch (button)
	{
	case DpadUp: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP); break;
	case DpadDown: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN); break;
	case DpadLeft: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT); break;
	case DpadRight: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT); break;
	case StartButton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_START); break;
	case BackButton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); break;
	case LeftThumb: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); break;
	case RightThumb: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); break;
	case LeftShoulder: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); break;
	case RightShoulder: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); break;
	case Abutton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_A); break;
	case Bbutton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_B); break;
	case Xbutton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_X); break;
	case Ybutton: return (xbox360state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); break;
	}

	return false;
}

/**********************************************************************************/
// get the trigger movement value (0 to 255)

int  Input::Xbox360Trigger(int trigger)
{
	switch (trigger)
	{
	case LTtrigger: return xbox360state.Gamepad.bLeftTrigger; break;
	case RTtrigger: return xbox360state.Gamepad.bRightTrigger; break;
	}

	return 0;
}

/**********************************************************************************/
// get the stick movement value (-32768 to 32767)

int  Input::Xbox360ThumbStick(int stick)
{
	switch (stick)
	{
	case LXthumb: 
		if (abs(xbox360state.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) 
			return xbox360state.Gamepad.sThumbLX;
		break;
	case LYthumb: 
		if (abs(xbox360state.Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) 
			return xbox360state.Gamepad.sThumbLY; 
		break;		
	case RXthumb: 
		if (abs(xbox360state.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) 
			return xbox360state.Gamepad.sThumbRX; 
		break;
	case RYthumb: 
		if (abs(xbox360state.Gamepad.sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) 
			return xbox360state.Gamepad.sThumbRY; 
		break;
	}

	return 0;
}

/**********************************************************************************/

void Input::Xbox360Vibrate(int controller, int left, int right)
{
	XINPUT_VIBRATION vibration;
	ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
	vibration.wLeftMotorSpeed = left;
	vibration.wRightMotorSpeed = right;
	XInputSetState(controller, &vibration);
}

/**********************************************************************************/